#include "jseditor.h"

#include "qjshighlight.h"
#include "qjsscanner.h"
#include "basetextdocumentlayout.h"
#include "refactoroverlay.h"
#include "autocompleter.h"
#include "indenter.h"
#include "semanticinfoupdater.h"
#include "semantichighlighter.h"
#include "texteditoroverlay.h"
#include "basetextfind.h"

#include "../js_lib/qmljsmodelmanagerinterface.h"
#include "../js_lib/qmljsdocument.h"
#include "../js_lib/qmljsutils.h"

#include <QPainter>
#include <QScrollBar>
#include <QStyle>
#include <QStyleOption>
#include <QApplication>


QList<JSEditor*>  g_editors;

using namespace QmlJS;
using namespace QmlJS::AST;

static QColor mergedColors(const QColor &colorA, const QColor &colorB, int factor)
{
    const int maxFactor = 100;
    QColor tmp = colorA;
    tmp.setRed((tmp.red() * factor) / maxFactor + (colorB.red() * (maxFactor - factor)) / maxFactor);
    tmp.setGreen((tmp.green() * factor) / maxFactor + (colorB.green() * (maxFactor - factor)) / maxFactor);
    tmp.setBlue((tmp.blue() * factor) / maxFactor + (colorB.blue() * (maxFactor - factor)) / maxFactor);
    return tmp;
}

static QColor blendColors(const QColor &a, const QColor &b, int alpha)
{
    return QColor((a.red()   * (256 - alpha) + b.red()   * alpha) / 256,
                  (a.green() * (256 - alpha) + b.green() * alpha) / 256,
                  (a.blue()  * (256 - alpha) + b.blue()  * alpha) / 256);
}

static QColor calcBlendColor(const QColor &baseColor, int level, int count)
{
    QColor color80;
    QColor color90;

    if (baseColor.value() > 128) {
        const int f90 = 15;
        const int f80 = 30;
        color80.setRgb(qMax(0, baseColor.red() - f80),
                       qMax(0, baseColor.green() - f80),
                       qMax(0, baseColor.blue() - f80));
        color90.setRgb(qMax(0, baseColor.red() - f90),
                       qMax(0, baseColor.green() - f90),
                       qMax(0, baseColor.blue() - f90));
    } else {
        const int f90 = 20;
        const int f80 = 40;
        color80.setRgb(qMin(255, baseColor.red() + f80),
                       qMin(255, baseColor.green() + f80),
                       qMin(255, baseColor.blue() + f80));
        color90.setRgb(qMin(255, baseColor.red() + f90),
                       qMin(255, baseColor.green() + f90),
                       qMin(255, baseColor.blue() + f90));
    }

    if (level == count)
        return baseColor;
    if (level == 0)
        return color80;
    if (level == count - 1)
        return color90;

    const int blendFactor = level * (256 / (count - 2));

    return blendColors(color80, color90, blendFactor);
}

static void drawRectBox(QPainter *painter, const QRect &rect, bool start, bool end,
                        const QPalette &pal)
{
    painter->save();
    painter->setRenderHint(QPainter::Antialiasing, false);

    QRgb b = pal.base().color().rgb();
    QRgb h = pal.highlight().color().rgb();
    QColor c = mergedColors(b,h, 50);

    QLinearGradient grad(rect.topLeft(), rect.topRight());
    grad.setColorAt(0, c.lighter(110));
    grad.setColorAt(1, c.lighter(130));
    QColor outline = c;

    painter->fillRect(rect, grad);
    painter->setPen(outline);
    if (start)
        painter->drawLine(rect.topLeft() + QPoint(1, 0), rect.topRight() -  QPoint(1, 0));
    if (end)
        painter->drawLine(rect.bottomLeft() + QPoint(1, 0), rect.bottomRight() -  QPoint(1, 0));

    painter->drawLine(rect.topRight() + QPoint(0, start ? 1 : 0), rect.bottomRight() - QPoint(0, end ? 1 : 0));
    painter->drawLine(rect.topLeft() + QPoint(0, start ? 1 : 0), rect.bottomLeft() - QPoint(0, end ? 1 : 0));

    painter->restore();
}

static inline bool isModifier(QKeyEvent *e)
{
    if (!e)
        return false;
    switch (e->key()) {
    case Qt::Key_Shift:
    case Qt::Key_Control:
    case Qt::Key_Meta:
    case Qt::Key_Alt:
        return true;
    default:
        return false;
    }
}

static QTextLine currentTextLine(const QTextCursor &cursor)
{
    const QTextBlock block = cursor.block();
    if (!block.isValid())
        return QTextLine();

    const QTextLayout *layout = block.layout();
    if (!layout)
        return QTextLine();

    const int relativePos = cursor.position() - block.position();
    return layout->lineForTextPosition(relativePos);
}


static void appendExtraSelectionsForMessages(
        QList<QTextEdit::ExtraSelection> *selections,
        const QList<DiagnosticMessage> &messages,
        const QTextDocument *document)
{
    foreach (const DiagnosticMessage &d, messages)
    {
        const int line = d.loc.startLine;
        const int column = qMax(1U, d.loc.startColumn);

        QTextEdit::ExtraSelection sel;
        QTextCursor c(document->findBlockByNumber(line - 1));
        sel.cursor = c;

        sel.cursor.setPosition(c.position() + column - 1);

        if (d.loc.length == 0) {
            if (sel.cursor.atBlockEnd())
                sel.cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
            else
                sel.cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
        } else {
            sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, d.loc.length);
        }

        if (d.isWarning())
            sel.format.setUnderlineColor(Qt::darkYellow);
        else
            sel.format.setUnderlineColor(Qt::red);

        sel.format.setUnderlineStyle(QTextCharFormat::WaveUnderline);
        sel.format.setToolTip(d.message);

        selections->append(sel);
    }
}

class SelectedElement: protected Visitor
{
    unsigned m_cursorPositionStart;
    unsigned m_cursorPositionEnd;
    QList<UiObjectMember *> m_selectedMembers;

public:
    SelectedElement()
        : m_cursorPositionStart(0), m_cursorPositionEnd(0) {}

    QList<UiObjectMember *> operator()(const Document::Ptr &doc, unsigned startPosition, unsigned endPosition)
    {
        m_cursorPositionStart = startPosition;
        m_cursorPositionEnd = endPosition;
        m_selectedMembers.clear();
        Node::accept(doc->qmlProgram(), this);
        return m_selectedMembers;
    }

protected:

    bool isSelectable(UiObjectMember *member) const
    {
        UiQualifiedId *id = qualifiedTypeNameId(member);
        if (id) {
            const QStringRef &name = id->name;
            if (!name.isEmpty() && name.at(0).isUpper())
                return true;
        }

        return false;
    }

    inline bool isIdBinding(UiObjectMember *member) const
    {
        if (UiScriptBinding *script = cast<UiScriptBinding *>(member)) {
            if (! script->qualifiedId)
                return false;
            else if (script->qualifiedId->name.isEmpty())
                return false;
            else if (script->qualifiedId->next)
                return false;

            const QStringRef &propertyName = script->qualifiedId->name;

            if (propertyName == QLatin1String("id"))
                return true;
        }

        return false;
    }

    inline bool containsCursor(unsigned begin, unsigned end)
    {
        return m_cursorPositionStart >= begin && m_cursorPositionEnd <= end;
    }

    inline bool intersectsCursor(unsigned begin, unsigned end)
    {
        return (m_cursorPositionEnd >= begin && m_cursorPositionStart <= end);
    }

    inline bool isRangeSelected() const
    {
        return (m_cursorPositionStart != m_cursorPositionEnd);
    }

    void postVisit(Node *ast)
    {
        if (!isRangeSelected() && !m_selectedMembers.isEmpty())
            return; // nothing to do, we already have the results.

        if (UiObjectMember *member = ast->uiObjectMemberCast()) {
            unsigned begin = member->firstSourceLocation().begin();
            unsigned end = member->lastSourceLocation().end();

            if ((isRangeSelected() && intersectsCursor(begin, end))
            || (!isRangeSelected() && containsCursor(begin, end)))
            {
                if (initializerOfObject(member) && isSelectable(member)) {
                    m_selectedMembers << member;
                    // move start towards end; this facilitates multiselection so that root is usually ignored.
                    m_cursorPositionStart = qMin(end, m_cursorPositionEnd);
                }
            }
        }
    }
};

class FindIdDeclarations: protected Visitor
{
public:
    typedef QHash<QString, QList<AST::SourceLocation> > Result;

    Result operator()(Document::Ptr doc)
    {
        _ids.clear();
        _maybeIds.clear();
        if (doc && doc->jsProgram())
            doc->jsProgram()->accept(this);
        return _ids;
    }

protected:
    QString asString(AST::UiQualifiedId *id)
    {
        QString text;
        for (; id; id = id->next) {
            if (!id->name.isEmpty())
                text += id->name;
            else
                text += QLatin1Char('?');

            if (id->next)
                text += QLatin1Char('.');
        }

        return text;
    }

    void accept(AST::Node *node)
    { AST::Node::acceptChild(node, this); }

    using Visitor::visit;
    using Visitor::endVisit;

    virtual bool visit(AST::UiScriptBinding *node)
    {
        if (asString(node->qualifiedId) == QLatin1String("id")) {
            if (AST::ExpressionStatement *stmt = AST::cast<AST::ExpressionStatement*>(node->statement)) {
                if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(stmt->expression)) {
                    if (!idExpr->name.isEmpty()) {
                        const QString &id = idExpr->name.toString();
                        QList<AST::SourceLocation> *locs = &_ids[id];
                        locs->append(idExpr->firstSourceLocation());
                        locs->append(_maybeIds.value(id));
                        _maybeIds.remove(id);
                        return false;
                    }
                }
            }
        }

        accept(node->statement);

        return false;
    }

    virtual bool visit(AST::IdentifierExpression *node)
    {
        if (!node->name.isEmpty()) {
            const QString &name = node->name.toString();

            if (_ids.contains(name))
                _ids[name].append(node->identifierToken);
            else
                _maybeIds[name].append(node->identifierToken);
        }
        return false;
    }

private:
    Result _ids;
    Result _maybeIds;
};

class CreateRanges: protected AST::Visitor
{
    QTextDocument *_textDocument;
    QList<Range> _ranges;

public:
    QList<Range> operator()(QTextDocument *textDocument, Document::Ptr doc)
    {
        _textDocument = textDocument;
        _ranges.clear();
        if (doc && doc->ast() != 0)
            doc->ast()->accept(this);
        return _ranges;
    }

protected:
    using AST::Visitor::visit;

    virtual bool visit(AST::UiObjectBinding *ast)
    {
        if (ast->initializer && ast->initializer->lbraceToken.length)
            _ranges.append(createRange(ast, ast->initializer));
        return true;
    }

    virtual bool visit(AST::UiObjectDefinition *ast)
    {
        if (ast->initializer && ast->initializer->lbraceToken.length)
            _ranges.append(createRange(ast, ast->initializer));
        return true;
    }

    virtual bool visit(AST::FunctionExpression *ast)
    {
        _ranges.append(createRange(ast));
        return true;
    }

    virtual bool visit(AST::FunctionDeclaration *ast)
    {
        _ranges.append(createRange(ast));
        return true;
    }

    virtual bool visit(AST::UiScriptBinding *ast)
    {
        if (AST::Block *block = AST::cast<AST::Block *>(ast->statement))
            _ranges.append(createRange(ast, block));
        return true;
    }

    Range createRange(AST::UiObjectMember *member, AST::UiObjectInitializer *ast)
    {
        return createRange(member, member->firstSourceLocation(), ast->rbraceToken);
    }

    Range createRange(AST::FunctionExpression *ast)
    {
        return createRange(ast, ast->lbraceToken, ast->rbraceToken);
    }

    Range createRange(AST::UiScriptBinding *ast, AST::Block *block)
    {
        return createRange(ast, block->lbraceToken, block->rbraceToken);
    }

    Range createRange(AST::Node *ast, AST::SourceLocation start, AST::SourceLocation end)
    {
        Range range;

        range.ast = ast;

        range.begin = QTextCursor(_textDocument);
        range.begin.setPosition(start.begin());

        range.end = QTextCursor(_textDocument);
        range.end.setPosition(end.end());

        return range;
    }

};

BaseTextEditorAnimator::BaseTextEditorAnimator(QObject *parent)
        :QObject(parent)
{
    m_value = 0;
    m_timeline = new QTimeLine(256, this);
    m_timeline->setCurveShape(QTimeLine::SineCurve);
    connect(m_timeline, SIGNAL(valueChanged(qreal)), this, SLOT(step(qreal)));
    connect(m_timeline, SIGNAL(finished()), this, SLOT(finish()));
    m_timeline->start();
}

BaseTextEditorAnimator::~BaseTextEditorAnimator()
{
    delete m_timeline;
}


void BaseTextEditorAnimator::setData(QFont f, QPalette pal, const QString &text)
{
    m_font = f;
    m_palette = pal;
    m_text = text;
    QFontMetrics fm(m_font);
    m_size = QSizeF(fm.width(m_text), fm.height());
}

void BaseTextEditorAnimator::draw(QPainter *p, const QPointF &pos)
{
    m_lastDrawPos = pos;
    p->setPen(m_palette.text().color());
    QFont f = m_font;
    f.setPointSizeF(f.pointSizeF() * (1.0 + m_value/2));
    QFontMetrics fm(f);
    int width = fm.width(m_text);
    QRectF r((m_size.width()-width)/2, (m_size.height() - fm.height())/2, width, fm.height());
    r.translate(pos);
    p->fillRect(r, m_palette.base());
    p->setFont(f);
    p->drawText(r, m_text);
}

bool BaseTextEditorAnimator::isRunning() const
{
    return m_timeline->state() == QTimeLine::Running;
}

QRectF BaseTextEditorAnimator::rect() const
{
    QFont f = m_font;
    f.setPointSizeF(f.pointSizeF() * (1.0 + m_value/2));
    QFontMetrics fm(f);
    int width = fm.width(m_text);
    return QRectF((m_size.width()-width)/2, (m_size.height() - fm.height())/2, width, fm.height());
}

void BaseTextEditorAnimator::step(qreal v)
{
    QRectF before = rect();
    m_value = v;
    QRectF after = rect();
    emit updateRequest(m_position, m_lastDrawPos, before.united(after));
}

void BaseTextEditorAnimator::finish()
{
    m_timeline->stop();
    step(0);
    deleteLater();
}

class QLineWidget : public QWidget
{
public:
    QLineWidget(QWidget* parent=0);
protected:
    void paintEvent(QPaintEvent *);
    QSize sizeHint()const;
    void mousePressEvent(QMouseEvent *e);
    void mouseMoveEvent(QMouseEvent *);
    void mouseReleaseEvent(QMouseEvent *);
    void wheelEvent(QWheelEvent *event);
    void leaveEvent(QEvent *event);
public:
    JSEditor    *m_editor;
    QPoint      m_mouse_pos;
    bool        m_is_clicked;
};

QLineWidget::QLineWidget(QWidget *parent):
    QWidget(parent),
    m_is_clicked(false)
{
    m_editor=(JSEditor*)parent;
    setMouseTracking(true);
}

void QLineWidget::wheelEvent(QWheelEvent *event)
{
    QCoreApplication::sendEvent(m_editor->viewport(), event);
    this->update();
}

void QLineWidget::paintEvent(QPaintEvent *)
{
    QPainter p(this);
    QRect re=this->rect().adjusted(0,0,1,0);
    p.fillRect(re,QBrush(QColor(240,240,240)));
    m_editor->painteLineWidget(&p);
}

void QLineWidget::leaveEvent(QEvent *event)
{
    m_editor->extraAreaLeaveEvent(event);
}

void QLineWidget::mouseMoveEvent(QMouseEvent *e)
{
    m_editor->extraAreaMouseMoveEvent(e);
}

void QLineWidget::mouseReleaseEvent(QMouseEvent *)
{
    m_is_clicked=false;
    update();
}

QSize QLineWidget::sizeHint()const
{
    return QSize(m_editor->extraAreaWidth(),0);
}

void QLineWidget::mousePressEvent(QMouseEvent *e)
{
    m_is_clicked=true;
    m_editor->extraAreaMousePressEvent(e);
}


JSEditor::JSEditor(QWidget *parent) :
    QPlainTextEdit(parent),
    m_line_widget(new QLineWidget(this)),
    m_extraAreaHighlightFoldedBlockNumber(-1),
    m_highlightBlocksTimer(new QTimer(this)),
    m_visibleFoldedBlockNumber(-1),
    m_suggestedVisibleFoldedBlockNumber(-1),
    m_refactorOverlay(new RefactorOverlay(this)),
    m_mouseOnFoldedMarker(false),
    m_contentsChanged(false),
    m_assistRelevantContentAdded(false),
    m_autocompleter(new AutoCompleter),
    m_indenter(new Indenter),
    m_parenthesesMatchingTimer(new QTimer(this)),
    m_animator(0),
    m_semanticinfo_updater(new SemanticInfoUpdater(this)),
    m_semanticHighlighter(new SemanticHighlighter(this)),
    m_oldCursorPosition(-1),
    m_text_find(new BaseTextFind(this)),
    m_findScopeVerticalBlockSelectionFirstColumn(-1),
    m_findScopeVerticalBlockSelectionLastColumn(-1)
{
    g_editors.append(this);
    m_semanticinfo_updater->start();
    connect(m_semanticinfo_updater,SIGNAL(updated(SemanticInfo)),this,SLOT(acceptNewSemanticInfo(SemanticInfo)));
    document()->setDocumentLayout(new BaseTextDocumentLayout(document()));
    m_high_light=new QJSHighLight(this->document());


    connect(m_text_find, SIGNAL(highlightAll(QString,FindFlags)),
            this, SLOT(highlightSearchResults(QString,FindFlags)));
    connect(m_text_find, SIGNAL(findScopeChanged(QTextCursor,QTextCursor,int,int)),
            this, SLOT(setFindScope(QTextCursor,QTextCursor,int,int)));

    m_overlay = new TextEditorOverlay(this);
    m_snippetOverlay = new TextEditorOverlay(this);
    m_searchResultOverlay = new TextEditorOverlay(this);

    m_occurrencesFormat.setForeground(Qt::black);
    m_occurrencesFormat.setBackground(QColor(190,190,190));

    m_modelManager = QmlJS::ModelManagerInterface::instance();

    if (m_modelManager) {
        connect(m_modelManager, SIGNAL(documentUpdated(QmlJS::Document::Ptr)),
                this, SLOT(onDocumentUpdated(QmlJS::Document::Ptr)));
        connect(m_modelManager, SIGNAL(libraryInfoUpdated(QString,QmlJS::LibraryInfo)),
                this, SLOT(updateSemanticInfo()));
        connect(this->document(), SIGNAL(modificationChanged(bool)), this, SLOT(modificationChanged(bool)));
    }

    connect(this,SIGNAL(textChanged()),this,SLOT(updateLineWidget()));
    connect(this->verticalScrollBar(),SIGNAL(valueChanged(int)),this,SLOT(updateLineWidget()));
    connect(this,SIGNAL(cursorPositionChanged()),this,SLOT(updateLineWidget()));
    connect(this,SIGNAL(cursorPositionChanged()),this,SLOT(cursorPositionChanged_slot()));
    m_highlightBlocksTimer->setSingleShot(true);
    connect(m_highlightBlocksTimer, SIGNAL(timeout()), this, SLOT(highlightBlocks()));

    m_ifdefedOutFormat.setForeground(Qt::black);

    QFont f=font();
    f.setFamily("Courier");
    f.setPointSize(13);
    setFont(f);
    viewport()->setMouseTracking(true);

    setWordWrapMode(QTextOption::NoWrap);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);

    m_parenthesesMatchingTimer->setSingleShot(true);
    connect(m_parenthesesMatchingTimer,SIGNAL(timeout()),this,SLOT(matchParentheses()));


    m_textMatchFormat.setForeground(Qt::red);
    m_matchFormat.setForeground(Qt::red);
    m_matchFormat.setBackground(QColor(0xb4, 0xee, 0xb4));
    m_mismatchFormat.setBackground(palette().color(QPalette::Base).value() < 128
                                      ? Qt::darkMagenta : Qt::magenta);

    m_updateSemanticInfoTimer = new QTimer(this);
    m_updateSemanticInfoTimer->setInterval(100);
    m_updateSemanticInfoTimer->setSingleShot(true);
    connect(m_updateSemanticInfoTimer, SIGNAL(timeout()), this, SLOT(updateSemanticInfoNow()));


    m_updateUsesTimer = new QTimer(this);
    m_updateUsesTimer->setInterval(150);
    m_updateUsesTimer->setSingleShot(true);
    connect(m_updateUsesTimer, SIGNAL(timeout()), this, SLOT(updateUsesNow()));


    connect(this, SIGNAL(textChanged()), this, SLOT(updateUses()));
    connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateUses()));


    m_updateDocumentTimer = new QTimer(this);
    m_updateDocumentTimer->setInterval(100);
    m_updateDocumentTimer->setSingleShot(true);
    connect(m_updateDocumentTimer, SIGNAL(timeout()), this, SLOT(reparseDocumentNow()));
    connect(this, SIGNAL(textChanged()), this, SLOT(reparseDocument()));

    m_cursorPositionTimer  = new QTimer(this);
    m_cursorPositionTimer->setInterval(500);
    m_cursorPositionTimer->setSingleShot(true);
    connect(m_cursorPositionTimer, SIGNAL(timeout()), this, SLOT(updateCursorPositionNow()));

}

JSEditor::~JSEditor()
{
    m_semanticinfo_updater->abort();
    m_semanticinfo_updater->wait();

    g_editors.removeAll(this);
}

void JSEditor::extraAreaMouseMoveEvent(QMouseEvent *e)
{
    if(e->buttons() == 0)
    {
        updateFoldingHighlight(e->pos());
    }
}


void JSEditor::updateFoldingHighlight(const QPoint &pos)
{
    QTextCursor cursor = cursorForPosition(QPoint(0, pos.y()));

    const int highlightBlockNumber = m_extraAreaHighlightFoldedBlockNumber;
    m_extraAreaHighlightFoldedBlockNumber = -1;

    if (pos.x() > m_line_widget->width()- 16)
    {
        m_extraAreaHighlightFoldedBlockNumber = cursor.blockNumber();
    }

    if (highlightBlockNumber != m_extraAreaHighlightFoldedBlockNumber)
        m_highlightBlocksTimer->start(m_highlightBlocksInfo.isEmpty() ? 120 : 0);
}

int JSEditor::extraAreaWidth()const
{
    int space=0;
    const QFontMetrics fm(fontMetrics());
    int digits=1;
    int max=qMax(1,document()->blockCount()+1);
    while(max>=10)
    {
        max/=10;
        ++digits;
    }
    space+=fm.width(QLatin1Char('9'))*digits;
    space+=8;
    space+=20;
    return space;
}

void JSEditor::painteLineWidget(QPainter *p)
{
    QRect re=rect();
    const QFontMetrics fm(fontMetrics());
    int extraAreaWidth=m_line_widget->width();
    QTextBlock block=firstVisibleBlock();
    int blockNumber=block.blockNumber();
    qreal top=blockBoundingGeometry(block).translated(contentOffset()).top()+2;
    qreal bottom=top+blockBoundingRect(block).height();

    while(block.isValid() && top<re.bottom())
    {
        QRectF re=blockBoundingRect(block);
        int lineNumber=blockNumber+1;
        QTextBlock nextBlock = block.next();
        QTextBlock nextVisibleBlock = nextBlock;
        int nextVisibleBlockNumber = blockNumber + 1;

        if (!nextVisibleBlock.isVisible())
        {
            nextVisibleBlock = document()->findBlockByLineNumber(nextVisibleBlock.firstLineNumber());
            nextVisibleBlockNumber = nextVisibleBlock.blockNumber();
        }
        if(block.isVisible())
        {
            if(blockNumber==textCursor().block().blockNumber())
            {
                p->setPen(QColor(40,40,40));
            }
            else
            {
                p->setPen(QColor(120,120,120));
            }
            QString number=QString::number(lineNumber);
            p->drawText(re.x(),(int)top,re.x()+extraAreaWidth-24,fm.height(),Qt::AlignRight,number);

        }
        draw_Foliding(p,block,top,bottom);
        block = nextVisibleBlock;
        top=bottom;
        bottom=top+re.height();
        blockNumber=nextVisibleBlockNumber;
    }
}

void JSEditor::draw_Foliding(QPainter *p, const QTextBlock &block, qreal top, qreal bottom)
{
    QPalette pal = m_line_widget->palette();
    QTextDocument *doc=document();
    QTextBlock nextBlock=block.next();
    int blockNumber=block.blockNumber();
    int extraAreaHighlightFoldBlockNumber = -1;
    int extraAreaHighlightFoldEndBlockNumber = -1;
    bool endIsVisible = false;
    if (!m_highlightBlocksInfo.isEmpty())
    {
        extraAreaHighlightFoldBlockNumber =  m_highlightBlocksInfo.open.last();
        extraAreaHighlightFoldEndBlockNumber =  m_highlightBlocksInfo.close.first();
        endIsVisible = doc->findBlockByNumber(extraAreaHighlightFoldEndBlockNumber).isVisible();
    }

    TextBlockUserData *nextBlockUserData = BaseTextDocumentLayout::testUserData(nextBlock);

    bool drawBox = nextBlockUserData
                   && BaseTextDocumentLayout::foldingIndent(block) < nextBlockUserData->foldingIndent();



    bool active = blockNumber == extraAreaHighlightFoldBlockNumber;

    bool drawStart = active;
    bool drawEnd = blockNumber == extraAreaHighlightFoldEndBlockNumber || (drawStart && !endIsVisible);
    bool hovered = blockNumber >= extraAreaHighlightFoldBlockNumber
                   && blockNumber <= extraAreaHighlightFoldEndBlockNumber;

    int boxWidth = 20;
    int left=m_line_widget->width()-boxWidth;
    int itop = qRound(top);
    int ibottom = qRound(bottom);
    if (hovered)
    {
        QRect box = QRect(left+1, itop, boxWidth - 2, ibottom - itop);
        drawRectBox(p, box, drawStart, drawEnd, pal);
    }

    if (drawBox)
    {
        bool expanded = nextBlock.isVisible();
        QRect box(left+1, top,boxWidth - 2,ibottom-itop);
        drawFoldingMarker(p, pal, box, expanded, active, hovered);
    }
}

void JSEditor::updateExtraAreaWidth()
{
    setViewportMargins(extraAreaWidth(),0,0,0);
}
void JSEditor::resizeEvent(QResizeEvent *e)
{
    QPlainTextEdit::resizeEvent(e);
    QRect re=contentsRect();
    m_line_widget->setGeometry(QRect(re.left(),re.top(),extraAreaWidth(),re.height()));
    updateExtraAreaWidth();
}

void JSEditor::updateLineWidget()
{
    updateExtraAreaWidth();
    m_line_widget->update();
}


void JSEditor::highlightBlocks()
{
    BaseTextEditorPrivateHighlightBlocks highlightBlocksInfo;

    QTextBlock block;
    if (m_extraAreaHighlightFoldedBlockNumber >= 0)
    {
        block = document()->findBlockByNumber(m_extraAreaHighlightFoldedBlockNumber);
        if (block.isValid()
            && block.next().isValid()
            && BaseTextDocumentLayout::foldingIndent(block.next())
            > BaseTextDocumentLayout::foldingIndent(block))
        {
            block = block.next();
        }
    }

    QTextBlock closeBlock = block;
    while (block.isValid())
    {
        int foldingIndent = BaseTextDocumentLayout::foldingIndent(block);

        while (block.previous().isValid()
               && BaseTextDocumentLayout::foldingIndent(block) >= foldingIndent)
        {
            block = block.previous();
        }

        int nextIndent = BaseTextDocumentLayout::foldingIndent(block);

        if (nextIndent == foldingIndent)
        {
            break;
        }
        highlightBlocksInfo.open.prepend(block.blockNumber());
        while (closeBlock.next().isValid()
            && BaseTextDocumentLayout::foldingIndent(closeBlock.next()) >= foldingIndent )
        {
            closeBlock = closeBlock.next();
        }
        highlightBlocksInfo.close.append(closeBlock.blockNumber());

        int visual_Indent = qMin(visualIndent(block), visualIndent(closeBlock));

        highlightBlocksInfo.visualIndent.prepend(visual_Indent);
    }

    if (m_highlightBlocksInfo != highlightBlocksInfo)
    {
        m_highlightBlocksInfo = highlightBlocksInfo;
        viewport()->update();
        m_line_widget->update();
    }
}



int JSEditor::visualIndent(const QTextBlock &block) const
{
    if (!block.isValid())
    {
        return 0;
    }

    const QTextDocument *document = block.document();
    int i = 0;
    while (i < block.length())
    {
        if (!document->characterAt(block.position() + i).isSpace())
        {
            QTextCursor cursor(block);
            cursor.setPosition(block.position() + i);
            return cursorRect(cursor).x();
        }
        ++i;
    }

    return 0;
}


void JSEditor::drawFoldingMarker(QPainter *painter, const QPalette &pal,
                                       const QRect &rect,
                                       bool expanded,
                                       bool active,
                                       bool hovered) const
{
    QStyleOption branchOption;
    branchOption.rect=rect;
    branchOption.palette=pal;
    branchOption.state=QStyle::State_Children;
    if(expanded)
    {
        branchOption.state |=QStyle::State_Open;
    }

    if (active)
    {
        branchOption.state |= QStyle::State_MouseOver | QStyle::State_Enabled | QStyle::State_Selected;
    }

    if (hovered)
    {
        branchOption.palette.setBrush(QPalette::Window, pal.highlight());
    }

    style()->drawPrimitive(QStyle::PE_IndicatorBranch,&branchOption,painter);
}

void JSEditor::extraAreaLeaveEvent(QEvent *)
{
    QMouseEvent me(QEvent::MouseMove, QPoint(-1, -1), Qt::NoButton, 0, 0);
    extraAreaMouseMoveEvent(&me);
}

void JSEditor::extraAreaMousePressEvent(QMouseEvent *e)
{
    if (e->button() == Qt::LeftButton)
    {
        QTextCursor cursor = cursorForPosition(QPoint(0, e->pos().y()));
        int boxWidth = 20;
        int left=m_line_widget->width()-boxWidth;
        if (e->pos().x() >left)
        {
            if (!cursor.block().next().isVisible())
            {
                toggleBlockVisible(cursor.block());
                moveCursorVisible(false);
            }
            else if (foldBox().contains(e->pos()))
            {
                cursor.setPosition(
                        document()->findBlockByNumber(m_highlightBlocksInfo.open.last()).position()
                        );
                QTextBlock c = cursor.block();
                toggleBlockVisible(c);
                moveCursorVisible(false);
            }
        }
        else
        {
            QTextCursor selection = cursor;
            selection.setVisualNavigation(true);
            //m_extraAreaSelectionAnchorBlockNumber = selection.blockNumber();
            selection.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
            selection.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
            setTextCursor(selection);
        }
    }
}

void JSEditor::toggleBlockVisible(const QTextBlock &block)
{
    BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(document()->documentLayout());

    BaseTextDocumentLayout::doFoldOrUnfold(block, BaseTextDocumentLayout::isFolded(block));
    documentLayout->requestUpdate();
    documentLayout->emitDocumentSizeChanged();
}

QRect JSEditor::foldBox()
{
    if (m_highlightBlocksInfo.isEmpty() || m_extraAreaHighlightFoldedBlockNumber < 0)
        return QRect();

    QTextBlock begin = document()->findBlockByNumber(m_highlightBlocksInfo.open.last());

    QTextBlock end = document()->findBlockByNumber(m_highlightBlocksInfo.close.first());
    if (!begin.isValid() || !end.isValid())
        return QRect();
    QRectF br = blockBoundingGeometry(begin).translated(contentOffset());
    QRectF er = blockBoundingGeometry(end).translated(contentOffset());

    return QRect(m_line_widget->width() - 20,
                 int(br.top()),
                 20,
                 er.bottom() - br.top());
}

void JSEditor::moveCursorVisible(bool ensureVisible)
{
    QTextCursor cursor = textCursor();
    if (!cursor.block().isVisible())
    {
        cursor.setVisualNavigation(true);
        cursor.movePosition(QTextCursor::Up);
        setTextCursor(cursor);
    }
    if (ensureVisible)
    {
        ensureCursorVisible();
    }
}

void JSEditor::paintEvent(QPaintEvent *e)
{
    QPainter painter(viewport());
    QTextDocument *doc = document();

    QPointF offset(contentOffset());

    bool suppressSyntaxInIfdefedOutBlock = (m_ifdefedOutFormat.foreground()
                                           != palette().foreground());

    QRect er = e->rect();
    QRect viewportRect = viewport()->rect();

    qreal lineX = 0;

    painter.setBrushOrigin(offset);

    bool editable = !isReadOnly();
    QTextBlock block = firstVisibleBlock();

    QAbstractTextDocumentLayout::PaintContext context = getPaintContext();

    if (!m_highlightBlocksInfo.isEmpty())
    {
        const QColor baseColor = palette().base().color();

        const int margin = 5;
        QTextBlock blockFP = block;
        QPointF offsetFP = offset;
        while (blockFP.isValid())
        {
            QRectF r = blockBoundingRect(blockFP).translated(offsetFP);

            int n = blockFP.blockNumber();
            int depth = 0;
            foreach (int i, m_highlightBlocksInfo.open)
                if (n >= i)
                    ++depth;
            foreach (int i, m_highlightBlocksInfo.close)
                if (n > i)
                    --depth;

            int count = m_highlightBlocksInfo.count();
            if (count)
            {
                for (int i = 0; i <= depth; ++i)
                {
                    const QColor &blendedColor = calcBlendColor(baseColor, i, count);
                    int vi = i > 0 ? m_highlightBlocksInfo.visualIndent.at(i-1) : 0;
                    QRectF oneRect = r;
                    oneRect.setWidth(viewport()->width());
                    oneRect.adjust(vi, 0, -8*i, 0);
                    if (oneRect.left() >= oneRect.right())
                        continue;
                    if (lineX > 0 && oneRect.left() < lineX && oneRect.right() > lineX) {
                        QRectF otherRect = r;
                        otherRect.setLeft(lineX + 1);
                        otherRect.setRight(oneRect.right());
                        oneRect.setRight(lineX - 1);
                        painter.fillRect(otherRect, blendedColor);
                    }
                    painter.fillRect(oneRect, blendedColor);
                }
            }
            offsetFP.ry() += r.height();

            if (offsetFP.y() > viewportRect.height() + margin)
                break;

            blockFP = blockFP.next();
            if (!blockFP.isVisible()) {
                blockFP = doc->findBlockByLineNumber(blockFP.firstLineNumber());
            }
        }
    }

    QTextBlock visibleCollapsedBlock;
    QPointF visibleCollapsedBlockOffset;

    QTextLayout *cursor_layout = 0;
    QPointF cursor_offset;
    int cursor_cpos = 0;
    QPen cursor_pen;

    while (block.isValid())
    {

        QRectF r = blockBoundingRect(block).translated(offset);

        if (r.bottom() >= er.top() && r.top() <= er.bottom()) {

            QTextLayout *layout = block.layout();

            QTextOption option = layout->textOption();
            if (suppressSyntaxInIfdefedOutBlock
                    && BaseTextDocumentLayout::ifdefedOut(block))
            {
                option.setFlags(option.flags() | QTextOption::SuppressColors);
                painter.setPen(m_ifdefedOutFormat.foreground().color());
            }
            else
            {
                option.setFlags(option.flags() & ~QTextOption::SuppressColors);
                painter.setPen(context.palette.text().color());
            }
            layout->setTextOption(option);
            layout->setFont(doc->defaultFont()); // this really should be in qplaintextedit when creating the layout!

            int blpos = block.position();
            int bllen = block.length();

            QVector<QTextLayout::FormatRange> selections;
            QVector<QTextLayout::FormatRange> prioritySelections;

            for (int i = 0; i < context.selections.size(); ++i)
            {
                const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i);
                const int selStart = range.cursor.selectionStart() - blpos;
                const int selEnd = range.cursor.selectionEnd() - blpos;
                if (selStart < bllen && selEnd >= 0
                    && selEnd >= selStart)
                {
                    QTextLayout::FormatRange o;
                    o.start = selStart;
                    o.length = selEnd - selStart;
                    o.format = range.format;
                    selections.append(o);
                }
            }
            selections += prioritySelections;

            QRectF blockSelectionCursorRect;

            bool drawCursor = ((editable || true) // we want the cursor in read-only mode
                               && context.cursorPosition >= blpos
                               && context.cursorPosition < blpos + bllen);

            bool drawCursorAsBlock = drawCursor && overwriteMode() ;

            if (drawCursorAsBlock)
            {
                int relativePos = context.cursorPosition - blpos;
                bool doSelection = true;
                QTextLine line = layout->lineForTextPosition(relativePos);
                qreal x = line.cursorToX(relativePos);
                qreal w = 0;
                if (relativePos < line.textLength() - line.textStart())
                {
                    w = line.cursorToX(relativePos + 1) - x;
                    if (doc->characterAt(context.cursorPosition) == QLatin1Char('\t'))
                    {
                        doSelection = false;
                        qreal space = QFontMetricsF(layout->font()).width(QLatin1Char(' '));
                        if (w > space)
                        {
                            x += w-space;
                            w = space;
                        }
                    }
                }
                else
                {
                    w = QFontMetrics(layout->font()).width(QLatin1Char(' ')); // in sync with QTextLine::draw()
                }
                QRectF rr = line.rect();
                rr.moveTop(rr.top() + r.top());
                rr.moveLeft(r.left() + x);
                rr.setWidth(w);
                painter.fillRect(rr, palette().text());
                if (doSelection)
                {
                    QTextLayout::FormatRange o;
                    o.start = relativePos;
                    o.length = 1;
                    o.format.setForeground(palette().base());
                    o.format.setBackground(palette().text());
                    selections.append(o);
                }
            }



            layout->draw(&painter, offset, selections, er);

            if ((drawCursor && !drawCursorAsBlock)
                || (editable && context.cursorPosition < -1
                    && !layout->preeditAreaText().isEmpty())) {
                int cpos = context.cursorPosition;
                if (cpos < -1)
                    cpos = layout->preeditAreaPosition() - (cpos + 2);
                else
                    cpos -= blpos;
                cursor_layout = layout;
                cursor_offset = offset;
                cursor_cpos = cpos;
                cursor_pen = painter.pen();
            }

            if (blockSelectionCursorRect.isValid())
                painter.fillRect(blockSelectionCursorRect, palette().text());
        }

        offset.ry() += r.height();

        if (offset.y() > viewportRect.height())
            break;

        block = block.next();

        if (!block.isVisible())
        {
            if (block.blockNumber() == m_visibleFoldedBlockNumber)
            {
                visibleCollapsedBlock = block;
                visibleCollapsedBlockOffset = offset + QPointF(0,1);
            }
            block = doc->findBlockByLineNumber(block.firstLineNumber());
        }
    }

    painter.setPen(context.palette.text().color());

    if (backgroundVisible() && !block.isValid() && offset.y() <= er.bottom()
        && (centerOnScroll() || verticalScrollBar()->maximum() == verticalScrollBar()->minimum())) {
        painter.fillRect(QRect(QPoint((int)er.left(), (int)offset.y()), er.bottomRight()), palette().background());
    }

    offset = contentOffset();
    block = firstVisibleBlock();

    qreal top = blockBoundingGeometry(block).translated(offset).top();
    qreal bottom = top + blockBoundingRect(block).height();

    QTextCursor cursor = textCursor();
    bool hasSelection = cursor.hasSelection();
    int selectionStart = cursor.selectionStart();
    int selectionEnd = cursor.selectionEnd();

    while (block.isValid() && top <= e->rect().bottom())
    {
        QTextBlock nextBlock = block.next();
        QTextBlock nextVisibleBlock = nextBlock;

        if (!nextVisibleBlock.isVisible())
        {
            nextVisibleBlock = doc->findBlockByLineNumber(nextVisibleBlock.firstLineNumber());
            while (nextVisibleBlock.isValid() && !nextVisibleBlock.isVisible())
            {
                nextVisibleBlock = nextVisibleBlock.next();
            }
        }
        if (block.isVisible() && bottom >= e->rect().top())
        {
            if (nextBlock.isValid() && !nextBlock.isVisible()) {

                bool selectThis = (hasSelection
                                   && nextBlock.position() >= selectionStart
                                   && nextBlock.position() < selectionEnd);
                painter.save();
                if (selectThis) {
                    painter.setBrush(palette().highlight());
                }

                QTextLayout *layout = block.layout();
                QTextLine line = layout->lineAt(layout->lineCount()-1);
                QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
                lineRect.adjust(0, 0, -1, -1);

                QString replacement = "...";
                QString rectReplacement = QLatin1String(" {") + replacement + QLatin1String("}; ");

                QRectF collapseRect(lineRect.right() + 12,
                                    lineRect.top(),
                                    fontMetrics().width(rectReplacement),
                                    lineRect.height());
                painter.setRenderHint(QPainter::Antialiasing, true);
                painter.translate(.5, .5);
                painter.drawRoundedRect(collapseRect.adjusted(0, 0, 0, 0), 3, 3);
                painter.setRenderHint(QPainter::Antialiasing, false);
                painter.translate(-.5, -.5);

                if (TextBlockUserData *nextBlockUserData = BaseTextDocumentLayout::testUserData(nextBlock)) {
                    if (nextBlockUserData->foldingStartIncluded())
                        replacement.prepend(nextBlock.text().trimmed().left(1));
                }

                block = nextVisibleBlock.previous();
                if (!block.isValid())
                    block = doc->lastBlock();

                if (TextBlockUserData *blockUserData = BaseTextDocumentLayout::testUserData(block)) {
                    if (blockUserData->foldingEndIncluded()) {
                        QString right = block.text().trimmed();
                        if (right.endsWith(QLatin1Char(';'))) {
                            right.chop(1);
                            right = right.trimmed();
                            replacement.append(right.right(right.endsWith(QLatin1Char('/')) ? 2 : 1));
                            replacement.append(QLatin1Char(';'));
                        } else {
                            replacement.append(right.right(right.endsWith(QLatin1Char('/')) ? 2 : 1));
                        }
                    }
                }

                if (selectThis)
                    painter.setPen(palette().highlightedText().color());
                painter.drawText(collapseRect.adjusted(0, 1, 0, 0), Qt::AlignCenter, replacement);
                painter.restore();
            }
        }

        block = nextVisibleBlock;
        top = bottom;
        bottom = top + blockBoundingRect(block).height();
    }

    if (m_animator!=NULL && m_animator->isRunning())
    {
        QTextCursor cursor = textCursor();
        cursor.setPosition(m_animator->position());
        m_animator->draw(&painter, cursorRect(cursor).topLeft());
    }

    if (m_findScopeStart.isNull()) {
        if (m_overlay->isVisible())
            m_overlay->paint(&painter, e->rect());

        if (m_snippetOverlay->isVisible())
            m_snippetOverlay->paint(&painter, e->rect());

        if (!m_refactorOverlay->isEmpty())
            m_refactorOverlay->paint(&painter, e->rect());
    }

    if (cursor_layout) {
        painter.setPen(cursor_pen);
        cursor_layout->drawCursor(&painter, cursor_offset, cursor_cpos, cursorWidth());
    }

    if (visibleCollapsedBlock.isValid()) {
        drawCollapsedBlockPopup(painter,
                                visibleCollapsedBlock,
                                visibleCollapsedBlockOffset,
                                er);
    }
}

void JSEditor::mouseMoveEvent(QMouseEvent *e)
{

    //updateLink(e);

    if (e->buttons() == Qt::NoButton)
    {
        const QTextBlock collapsedBlock = foldedBlockAt(e->pos());
        const int blockNumber = collapsedBlock.next().blockNumber();
        if (blockNumber < 0)
        {
            clearVisibleFoldedBlock();
        }
        else if (blockNumber != m_visibleFoldedBlockNumber)
        {
            m_suggestedVisibleFoldedBlockNumber = blockNumber;
            m_foldedBlockTimer.start(40, this);
        }

        const RefactorMarker refactorMarker = m_refactorOverlay->markerAt(e->pos());

        // Update the mouse cursor
        if ((collapsedBlock.isValid()
             || refactorMarker.isValid())
                && !m_mouseOnFoldedMarker) {
            m_mouseOnFoldedMarker = true;
            viewport()->setCursor(Qt::PointingHandCursor);
        }
        else if (!collapsedBlock.isValid()
                 && !refactorMarker.isValid()
                 && m_mouseOnFoldedMarker)
        {
            m_mouseOnFoldedMarker = false;
            viewport()->setCursor(Qt::IBeamCursor);
        }
    }
    else
    {
        QPlainTextEdit::mouseMoveEvent(e);
    }
    if (viewport()->cursor().shape() == Qt::BlankCursor)
    {
        viewport()->setCursor(Qt::IBeamCursor);
    }
}


QTextBlock JSEditor::foldedBlockAt(const QPoint &pos, QRect *box) const
{
    QPointF offset(contentOffset());
    QTextBlock block = firstVisibleBlock();
    qreal top = blockBoundingGeometry(block).translated(offset).top();
    qreal bottom = top + blockBoundingRect(block).height();

    int viewportHeight = viewport()->height();

    while (block.isValid() && top <= viewportHeight)
    {
        QTextBlock nextBlock = block.next();
        if (block.isVisible() && bottom >= 0)
        {
            if (nextBlock.isValid() && !nextBlock.isVisible())
            {
                QTextLayout *layout = block.layout();
                QTextLine line = layout->lineAt(layout->lineCount()-1);
                QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
                lineRect.adjust(0, 0, -1, -1);

                QString replacement = QLatin1String(" {") + "..."
                        + QLatin1String("}; ");

                QRectF collapseRect(lineRect.right() + 12,
                                    lineRect.top(),
                                    fontMetrics().width(replacement),
                                    lineRect.height());
                if (collapseRect.contains(pos))
                {
                    QTextBlock result = block;
                    if (box)
                        *box = collapseRect.toAlignedRect();
                    return result;
                } else
                {
                    block = nextBlock;
                    while (nextBlock.isValid() && !nextBlock.isVisible())
                    {
                        block = nextBlock;
                        nextBlock = block.next();
                    }
                }
            }
        }

        block = nextBlock;
        top = bottom;
        bottom = top + blockBoundingRect(block).height();
    }
    return QTextBlock();
}

void JSEditor::clearVisibleFoldedBlock()
{
    if (m_suggestedVisibleFoldedBlockNumber) {
        m_suggestedVisibleFoldedBlockNumber = -1;
        m_foldedBlockTimer.stop();
    }
    if (m_visibleFoldedBlockNumber >= 0) {
        m_visibleFoldedBlockNumber = -1;
        viewport()->update();
    }
}

void JSEditor::timerEvent(QTimerEvent *e)
{
    if (e->timerId() == m_foldedBlockTimer.timerId()) {
        m_visibleFoldedBlockNumber = m_suggestedVisibleFoldedBlockNumber;
        m_suggestedVisibleFoldedBlockNumber = -1;
        m_foldedBlockTimer.stop();
        viewport()->update();
    }
    QPlainTextEdit::timerEvent(e);
}

void JSEditor::drawCollapsedBlockPopup(QPainter &painter,
                                             const QTextBlock &block,
                                             QPointF offset,
                                             const QRect &clip)
{
    int margin = block.document()->documentMargin();
    qreal maxWidth = 0;
    qreal blockHeight = 0;
    QTextBlock b = block;

    while (!b.isVisible())
    {
        b.setVisible(true);
        QRectF r = blockBoundingRect(b).translated(offset);

        QTextLayout *layout = b.layout();
        for (int i = layout->lineCount()-1; i >= 0; --i)
            maxWidth = qMax(maxWidth, layout->lineAt(i).naturalTextWidth() + 2*margin);

        blockHeight += r.height();

        b.setVisible(false); // restore previous state
        b.setLineCount(0); // restore 0 line count for invisible block
        b = b.next();
    }

    painter.save();
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.translate(.5, .5);
    QBrush brush = QColor(220,220,220);
    painter.setBrush(brush);
    painter.drawRoundedRect(QRectF(offset.x(),
                                   offset.y(),
                                   maxWidth, blockHeight).adjusted(0, 0, 0, 0), 3, 3);
    painter.restore();

    QTextBlock end = b;
    b = block;
    while (b != end)
    {
        b.setVisible(true); // make sure block bounding rect works
        QRectF r = blockBoundingRect(b).translated(offset);
        QTextLayout *layout = b.layout();
        QVector<QTextLayout::FormatRange> selections;
        layout->draw(&painter, offset, selections, clip);

        b.setVisible(false); // restore previous state
        b.setLineCount(0); // restore 0 line count for invisible block
        offset.ry() += r.height();
        b = b.next();
    }
}

void JSEditor::mousePressEvent(QMouseEvent *e)
{
    if (e->button() == Qt::LeftButton)
    {
        QTextBlock foldedBlock = foldedBlockAt(e->pos());
        if (foldedBlock.isValid())
        {
            toggleBlockVisible(foldedBlock);
            viewport()->setCursor(Qt::IBeamCursor);
            m_line_widget->update();
        }

        RefactorMarker refactorMarker = m_refactorOverlay->markerAt(e->pos());
        if (refactorMarker.isValid())
        {

        }
        else
        {

        }
    }
    else if (e->button() == Qt::RightButton)
    {
        int eventCursorPosition = cursorForPosition(e->pos()).position();
        if (eventCursorPosition < textCursor().selectionStart()
                || eventCursorPosition > textCursor().selectionEnd()) {
            setTextCursor(cursorForPosition(e->pos()));
        }
    }

    QPlainTextEdit::mousePressEvent(e);
}

void JSEditor::focusInEvent(QFocusEvent *e)
{
    QPlainTextEdit::focusInEvent(e);
}

void JSEditor::focusOutEvent(QFocusEvent *e)
{
    QPlainTextEdit::focusOutEvent(e);
    if (viewport()->cursor().shape() == Qt::BlankCursor)
        viewport()->setCursor(Qt::IBeamCursor);
}

void JSEditor::wheelEvent(QWheelEvent *e)
{
    clearVisibleFoldedBlock();
    QPlainTextEdit::wheelEvent(e);
}
void JSEditor::editorContentsChange(int position, int charsRemoved, int charsAdded)
{
    m_contentsChanged = true;
    QTextDocument *doc = document();
    BaseTextDocumentLayout *documentLayout = static_cast<BaseTextDocumentLayout*>(doc->documentLayout());

    if (charsRemoved != 0) {
        documentLayout->updateMarksLineNumber();
        documentLayout->updateMarksBlock(document()->findBlock(position));
    } else {
        const QTextBlock posBlock = doc->findBlock(position);
        const QTextBlock nextBlock = doc->findBlock(position + charsAdded);
        if (posBlock != nextBlock) {
            documentLayout->updateMarksLineNumber();
            documentLayout->updateMarksBlock(posBlock);
            documentLayout->updateMarksBlock(nextBlock);
        } else {
            documentLayout->updateMarksBlock(posBlock);
        }
    }

    if (charsAdded != 0 && document()->characterAt(position + charsAdded - 1).isPrint())
        m_assistRelevantContentAdded = true;
}

void JSEditor::keyPressEvent(QKeyEvent *e)
{
    clearVisibleFoldedBlock();

    bool ro = isReadOnly();

    if (!ro
        && (e == QKeySequence::InsertParagraphSeparator
            || (e == QKeySequence::InsertLineSeparator))) {

        QTextCursor cursor = textCursor();
        cursor.beginEditBlock();

        int extraBlocks =
            m_autocompleter->paragraphSeparatorAboutToBeInserted(cursor, m_tab_settings);

        cursor.insertBlock();
        indent(document(), cursor, QChar::Null);
        cursor.endEditBlock();
        e->accept();

        if (extraBlocks > 0)
        {
            QTextCursor ensureVisible = cursor;
            while (extraBlocks > 0)
            {
                --extraBlocks;
                ensureVisible.movePosition(QTextCursor::NextBlock);
                indent(document(), ensureVisible, QChar::Null);
            }
            setTextCursor(ensureVisible);
        }

        setTextCursor(cursor);
        return;
    }
    else if (!ro
                   && (e == QKeySequence::MoveToStartOfLine
                       || e == QKeySequence::SelectStartOfLine))
    {
            if ((e->modifiers() & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier))
            {
                    e->accept();
                    return;
            }
            QTextCursor cursor = textCursor();
            if (QTextLayout *layout = cursor.block().layout())
            {
                if (layout->lineForTextPosition(cursor.position() - cursor.block().position()).lineNumber() == 0)
                {
                    handleHomeKey(e == QKeySequence::SelectStartOfLine);
                    e->accept();
                    return;
                }
            }return;
   }
    else if (!ro && e == QKeySequence::DeleteEndOfWord && !textCursor().hasSelection())
    {
        e->accept();
        QTextCursor c = textCursor();
        camelCaseRight(c, QTextCursor::KeepAnchor);
        c.removeSelectedText();
        return;
    }else
    {
        switch (e->key())
        {
        case Qt::Key_Tab:
        case Qt::Key_Backtab:
        {
            if (ro) break;
            indentOrUnindent(e->key() == Qt::Key_Tab);
            e->accept();
            return;
        }
            break;
        case Qt::Key_Backspace:
            if (ro) break;
            if ((e->modifiers() & (Qt::ControlModifier
                                   | Qt::ShiftModifier
                                   | Qt::AltModifier
                                   | Qt::MetaModifier)) == Qt::NoModifier
                && !textCursor().hasSelection()) {
                handleBackspaceKey();
                e->accept();
                return;
            }
            break;
        case Qt::Key_Up:
        case Qt::Key_Down:
            if (e->modifiers() & Qt::ControlModifier) {
                verticalScrollBar()->triggerAction(
                        e->key() == Qt::Key_Up ? QAbstractSlider::SliderSingleStepSub :
                                                 QAbstractSlider::SliderSingleStepAdd);
                e->accept();
                return;
            }
            break;
        case Qt::Key_PageUp:
        case Qt::Key_PageDown:
            if (e->modifiers() == Qt::ControlModifier) {
                verticalScrollBar()->triggerAction(
                        e->key() == Qt::Key_PageUp ? QAbstractSlider::SliderPageStepSub :
                                                     QAbstractSlider::SliderPageStepAdd);
                e->accept();
                return;
            }
        default:
            break;
        }
    }


    if (ro || e->text().isEmpty() || !e->text().at(0).isPrint()) {
        if (!cursorMoveKeyEvent(e)) {
            //QTextCursor cursor = textCursor();
            //bool cursorWithinSnippet = false;
            //if (d->m_snippetOverlay->isVisible()
            //    && (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace)) {
            //    cursorWithinSnippet = d->snippetCheckCursor(cursor);
            //}
            //if (cursorWithinSnippet)
            //    cursor.beginEditBlock();

            QPlainTextEdit::keyPressEvent(e);

            //if (cursorWithinSnippet) {
            //    cursor.endEditBlock();
            //    d->m_snippetOverlay->updateEquivalentSelections(textCursor());
            //}
        }
    }
    else if ((e->modifiers() & (Qt::ControlModifier|Qt::AltModifier)) != Qt::ControlModifier)
    {
        QTextCursor cursor = textCursor();
        QString text = e->text();
        const QString &autoText = m_autocompleter->autoComplete(cursor, text);

        QChar electricChar;
        foreach (QChar c, text)
        {
            if (m_indenter->isElectricCharacter(c))
            {
                electricChar = c;
                break;
            }
        }

        //bool cursorWithinSnippet = false;
        //if (d->m_snippetOverlay->isVisible())
        //    cursorWithinSnippet = d->snippetCheckCursor(cursor);

        bool doEditBlock = !electricChar.isNull() || !autoText.isEmpty();// || cursorWithinSnippet;
        if (doEditBlock)
            cursor.beginEditBlock();
        cursor.insertText(text);

        if (!autoText.isEmpty())
        {
            int pos = cursor.position();
            cursor.insertText(autoText);
            cursor.setPosition(pos, QTextCursor::KeepAnchor);
        }
        if (!electricChar.isNull() && m_autocompleter->contextAllowsElectricCharacters(cursor))
            indent(document(), cursor, electricChar);
        if (!autoText.isEmpty())
            cursor.setPosition(autoText.length() == 1 ? cursor.position() : cursor.anchor());

        if (doEditBlock) {
            cursor.endEditBlock();
            //if (cursorWithinSnippet)
            //    d->m_snippetOverlay->updateEquivalentSelections(textCursor());
        }

        setTextCursor(cursor);
    }

}

void JSEditor::indent(QTextDocument *doc, const QTextCursor &cursor, QChar typedChar)
{
    //maybeClearSomeExtraSelections(cursor);
    m_indenter->indent(doc, cursor, typedChar, m_tab_settings);
}

void JSEditor::handleHomeKey(bool anchor)
{
    QTextCursor cursor = textCursor();
    QTextCursor::MoveMode mode = QTextCursor::MoveAnchor;

    if (anchor)
        mode = QTextCursor::KeepAnchor;

    const int initpos = cursor.position();
    int pos = cursor.block().position();
    QChar character = document()->characterAt(pos);
    const QLatin1Char tab = QLatin1Char('\t');

    while (character == tab || character.category() == QChar::Separator_Space)
    {
        ++pos;
        if (pos == initpos)
            break;
        character = document()->characterAt(pos);
    }

    if (pos == initpos)
    {
        pos = cursor.block().position();
    }

    cursor.setPosition(pos, mode);
    setTextCursor(cursor);
}

bool JSEditor::camelCaseRight(QTextCursor &cursor, QTextCursor::MoveMode mode)
{
    int state = 0;
    enum Input {
        Input_U,
        Input_l,
        Input_underscore,
        Input_space,
        Input_other
    };

    forever {
        QChar c = document()->characterAt(cursor.position());
        Input input = Input_other;
        if (c.isUpper())
            input = Input_U;
        else if (c.isLower() || c.isDigit())
            input = Input_l;
        else if (c == QLatin1Char('_'))
            input = Input_underscore;
        else if (c.isSpace() && c != QChar::ParagraphSeparator)
            input = Input_space;
        else
            input = Input_other;

        switch (state) {
        case 0:
            switch (input) {
            case Input_U:
                state = 4;
                break;
            case Input_l:
                state = 1;
                break;
            case Input_underscore:
                state = 6;
                break;
            default:
                return cursor.movePosition(QTextCursor::WordRight, mode);
            }
            break;
        case 1:
            switch (input) {
            case Input_U:
                return true;
            case Input_l:
                break;
            case Input_underscore:
                state = 6;
                break;
            case Input_space:
                state = 7;
                break;
            default:
                return true;
            }
            break;
        case 2:
            switch (input) {
            case Input_U:
                break;
            case Input_l:
                cursor.movePosition(QTextCursor::Left, mode);
                return true;
            case Input_underscore:
                state = 6;
                break;
            case Input_space:
                state = 7;
                break;
            default:
                return true;
            }
            break;
        case 4:
            switch (input) {
            case Input_U:
                state = 2;
                break;
            case Input_l:
                state = 1;
                break;
            case Input_underscore:
                state = 6;
                break;
            case Input_space:
                state = 7;
                break;
            default:
                return true;
            }
            break;
        case 6:
            switch (input) {
            case Input_underscore:
                break;
            case Input_space:
                state = 7;
                break;
            default:
                return true;
            }
            break;
        case 7:
            switch (input) {
            case Input_space:
                break;
            default:
                return true;
            }
            break;
        }
        cursor.movePosition(QTextCursor::Right, mode);
    }
}

bool JSEditor::camelCaseLeft(QTextCursor &cursor, QTextCursor::MoveMode mode)
{
    int state = 0;
    enum Input {
        Input_U,
        Input_l,
        Input_underscore,
        Input_space,
        Input_other
    };

    if (!cursor.movePosition(QTextCursor::Left, mode))
        return false;

    forever {
        QChar c = document()->characterAt(cursor.position());
        Input input = Input_other;
        if (c.isUpper())
            input = Input_U;
        else if (c.isLower() || c.isDigit())
            input = Input_l;
        else if (c == QLatin1Char('_'))
            input = Input_underscore;
        else if (c.isSpace() && c != QChar::ParagraphSeparator)
            input = Input_space;
        else
            input = Input_other;

        switch (state) {
        case 0:
            switch (input) {
            case Input_U:
                state = 1;
                break;
            case Input_l:
                state = 2;
                break;
            case Input_underscore:
                state = 3;
                break;
            case Input_space:
                state = 4;
                break;
            default:
                cursor.movePosition(QTextCursor::Right, mode);
                return cursor.movePosition(QTextCursor::WordLeft, mode);
            }
            break;
        case 1:
            switch (input) {
            case Input_U:
                break;
            default:
                cursor.movePosition(QTextCursor::Right, mode);
                return true;
            }
            break;

        case 2:
            switch (input) {
            case Input_U:
                return true;
            case Input_l:
                break;
            default:
                cursor.movePosition(QTextCursor::Right, mode);
                return true;
            }
            break;
        case 3:
            switch (input) {
            case Input_underscore:
                break;
            case Input_U:
                state = 1;
                break;
            case Input_l:
                state = 2;
                break;
            default:
                cursor.movePosition(QTextCursor::Right, mode);
                return true;
            }
            break;
        case 4:
            switch (input) {
            case Input_space:
                break;
            case Input_U:
                state = 1;
                break;
            case Input_l:
                state = 2;
                break;
            case Input_underscore:
                state = 3;
                break;
            default:
                cursor.movePosition(QTextCursor::Right, mode);
                if (cursor.positionInBlock() == 0)
                    return true;
                return cursor.movePosition(QTextCursor::WordLeft, mode);
            }
        }

        if (!cursor.movePosition(QTextCursor::Left, mode))
            return true;
    }
}

void JSEditor::cursorPositionChanged_slot()
{
    if (extraSelections(ParenthesesMatchingSelection).isEmpty()
        && m_animator == 0)
    {
        m_parenthesesMatchingTimer->start(50);
    }
    else
    {
        //if (!d->m_displaySettings.m_highlightMatchingParentheses)
            setExtraSelections(ParenthesesMatchingSelection,QList<QTextEdit::ExtraSelection>());

        // use 0-timer, not direct call, to give the syntax highlighter a chance
        // to update the parentheses information
        m_parenthesesMatchingTimer->start(0);
    }
}

void JSEditor::matchParentheses()
{
    QTextCursor backwardMatch = textCursor();
    QTextCursor forwardMatch = textCursor();

    const TextBlockUserData::MatchType backwardMatchType = TextBlockUserData::matchCursorBackward(&backwardMatch);
    const TextBlockUserData::MatchType forwardMatchType = TextBlockUserData::matchCursorForward(&forwardMatch);

    QList<QTextEdit::ExtraSelection> extraSelection;

    if (backwardMatchType == TextBlockUserData::NoMatch
            && forwardMatchType == TextBlockUserData::NoMatch) {
        setExtraSelections(ParenthesesMatchingSelection, extraSelection);
        return;
    }

    int animatePosition = -1;
    if (backwardMatch.hasSelection()) {
        QTextEdit::ExtraSelection sel;
        if (backwardMatchType == TextBlockUserData::Mismatch) {
            sel.cursor = backwardMatch;
            sel.format = m_mismatchFormat;
            extraSelection.append(sel);
        } else {

            sel.cursor = backwardMatch;
            sel.format = m_textMatchFormat;

            sel.cursor.setPosition(backwardMatch.selectionStart());
            sel.cursor.setPosition(sel.cursor.position() + 1, QTextCursor::KeepAnchor);
            extraSelection.append(sel);

            if (sel.cursor.block().isVisible())
                animatePosition = backwardMatch.selectionStart();

            sel.cursor.setPosition(backwardMatch.selectionEnd());
            sel.cursor.setPosition(sel.cursor.position() - 1, QTextCursor::KeepAnchor);
            extraSelection.append(sel);
        }
    }

    if (forwardMatch.hasSelection()) {
        QTextEdit::ExtraSelection sel;
        if (forwardMatchType == TextBlockUserData::Mismatch) {
            sel.cursor = forwardMatch;
            sel.format = m_mismatchFormat;
            extraSelection.append(sel);
        } else {

            sel.cursor = forwardMatch;
            sel.format = m_textMatchFormat;

            sel.cursor.setPosition(forwardMatch.selectionStart());
            sel.cursor.setPosition(sel.cursor.position() + 1, QTextCursor::KeepAnchor);
            extraSelection.append(sel);

            sel.cursor.setPosition(forwardMatch.selectionEnd());
            sel.cursor.setPosition(sel.cursor.position() - 1, QTextCursor::KeepAnchor);
            extraSelection.append(sel);

            if (sel.cursor.block().isVisible())
                animatePosition = forwardMatch.selectionEnd() - 1;
        }
    }


    if (animatePosition >= 0)
    {
        foreach (const QTextEdit::ExtraSelection &sel, extraSelections(ParenthesesMatchingSelection))
        {
            if (sel.cursor.selectionStart() == animatePosition
                || sel.cursor.selectionEnd() - 1 == animatePosition)
            {
                animatePosition = -1;
                break;
            }
        }
    }

    if (animatePosition >= 0)
    {
        if (m_animator)
            m_animator->finish();  // one animation is enough
        m_animator = new BaseTextEditorAnimator(this);
        m_animator->setPosition(animatePosition);
        QPalette pal;
        pal.setBrush(QPalette::Text, m_matchFormat.foreground());
        pal.setBrush(QPalette::Base, m_matchFormat.background());
        m_animator->setData(font(), pal, document()->characterAt(m_animator->position()));
        connect(m_animator, SIGNAL(updateRequest(int,QPointF,QRectF)),
                this, SLOT(animateUpdate(int,QPointF,QRectF)));
        connect(m_animator,SIGNAL(destroyed()),this,SLOT(animate_delete()));
    }
    setExtraSelections(ParenthesesMatchingSelection,extraSelection);
}

void JSEditor::animateUpdate(int position, QPointF lastPos, QRectF rect)
{
    QTextCursor cursor(textCursor());
    cursor.setPosition(position);
    viewport()->update(QRectF(cursorRect(cursor).topLeft() + rect.topLeft(), rect.size()).toAlignedRect());
    if (!lastPos.isNull())
        viewport()->update(QRectF(lastPos + rect.topLeft(), rect.size()).toAlignedRect());
}

void JSEditor::setExtraSelections(ExtraSelectionKind kind, const QList<QTextEdit::ExtraSelection> &selections)
{
    if (selections.isEmpty() && m_extraSelections[kind].isEmpty())
        return;
    m_extraSelections[kind] = selections;

    if (kind == CodeSemanticsSelection)
    {
        m_overlay->clear();
        foreach (const QTextEdit::ExtraSelection &selection, m_extraSelections[kind]) {
            m_overlay->addOverlaySelection(selection.cursor,
                                              selection.format.background().color(),
                                              selection.format.background().color(),
                                              TextEditorOverlay::LockSize);
        }
        m_overlay->setVisible(!m_overlay->isEmpty());
    }
    else if (kind == SnippetPlaceholderSelection)
    {
        /*d->m_snippetOverlay->clear();
        foreach (const QTextEdit::ExtraSelection &selection, d->m_extraSelections[kind]) {
            d->m_snippetOverlay->addOverlaySelection(selection.cursor,
                                              selection.format.background().color(),
                                              selection.format.background().color(),
                                              TextEditorOverlay::ExpandBegin);
        }
        d->m_snippetOverlay->mapEquivalentSelections();
        d->m_snippetOverlay->setVisible(!d->m_snippetOverlay->isEmpty());*/
    }
    else
    {
        QList<QTextEdit::ExtraSelection> all;
        for (int i = 0; i < NExtraSelectionKinds; ++i) {
            if (i == CodeSemanticsSelection || i == SnippetPlaceholderSelection)
                continue;
            all += m_extraSelections[i];
        }
        QPlainTextEdit::setExtraSelections(all);
    }
}

void JSEditor::animate_delete()
{
    m_animator=NULL;
}

bool JSEditor::tabShouldIndent(const QTextDocument *document, QTextCursor cursor, int *suggestedPosition) const
{
    QTextCursor tc = cursor;
    if (suggestedPosition)
        *suggestedPosition = tc.position(); // At least suggest original position
    tc.movePosition(QTextCursor::StartOfLine);
    if (tc.atBlockEnd()) // cursor was on a blank line
        return true;
    if (document->characterAt(tc.position()).isSpace())
    {
        tc.movePosition(QTextCursor::WordRight);
        if (tc.positionInBlock() >= cursor.positionInBlock()) {
            if (suggestedPosition)
                *suggestedPosition = tc.position(); // Suggest position after whitespace
            return true;
        }
    }
    return false;
}

void JSEditor::indentOrUnindent(bool doIndent)
{
    QTextCursor cursor = textCursor();
    maybeClearSomeExtraSelections(cursor);
    cursor.beginEditBlock();

    if (cursor.hasSelection())
    {
        int pos = cursor.position();
        int anchor = cursor.anchor();
        int start = qMin(anchor, pos);
        int end = qMax(anchor, pos);

        QTextDocument *doc = document();
        QTextBlock startBlock = doc->findBlock(start);
        QTextBlock endBlock = doc->findBlock(end-1).next();

        if (startBlock.next() == endBlock
                && (start > startBlock.position() || end < endBlock.position() - 1))
        {
            cursor.removeSelectedText();
        }
        else
        {
            for (QTextBlock block = startBlock; block != endBlock; block = block.next())
            {
                QString text = block.text();
                int indentPosition = m_tab_settings.lineIndentPosition(text);
                if (!doIndent && !indentPosition)
                    indentPosition = m_tab_settings.firstNonSpace(text);
                int targetColumn = m_tab_settings.indentedColumn(m_tab_settings.columnAt(text, indentPosition), doIndent);
                cursor.setPosition(block.position() + indentPosition);
                cursor.insertText(m_tab_settings.indentationString(0, targetColumn, block));
                cursor.setPosition(block.position());
                cursor.setPosition(block.position() + indentPosition, QTextCursor::KeepAnchor);
                cursor.removeSelectedText();
            }
            cursor.endEditBlock();
            return;
        }
    }

    // Indent or unindent at cursor position
    QTextBlock block = cursor.block();
    QString text = block.text();
    int indentPosition = cursor.positionInBlock();
    int spaces = m_tab_settings.spacesLeftFromPosition(text, indentPosition);
    int startColumn = m_tab_settings.columnAt(text, indentPosition - spaces);
    int targetColumn = m_tab_settings.indentedColumn(m_tab_settings.columnAt(text, indentPosition), doIndent);
    cursor.setPosition(block.position() + indentPosition);
    cursor.setPosition(block.position() + indentPosition - spaces, QTextCursor::KeepAnchor);
    cursor.removeSelectedText();
    cursor.insertText(m_tab_settings.indentationString(startColumn, targetColumn, block));
    cursor.endEditBlock();
    setTextCursor(cursor);
}

void JSEditor::maybeClearSomeExtraSelections(const QTextCursor &cursor)
{
    const int smallSelectionSize = 50 * 50;
    if (cursor.selectionEnd() - cursor.selectionStart() < smallSelectionSize)
        return;

    m_extraSelections[UndefinedSymbolSelection].clear();
    m_extraSelections[ObjCSelection].clear();
    m_extraSelections[CodeWarningsSelection].clear();

    QList<QTextEdit::ExtraSelection> all;
    for (int i = 0; i < NExtraSelectionKinds; ++i) {
        if (i == CodeSemanticsSelection || i == SnippetPlaceholderSelection)
            continue;
        all += m_extraSelections[i];
    }
    QPlainTextEdit::setExtraSelections(all);
}

void JSEditor::handleBackspaceKey()
{
    QTextCursor cursor = textCursor();

    const int pos = cursor.position();
    if (!pos)
        return;
    if (m_autocompleter->autoBackspace(cursor))
        return;
    cursor.deletePreviousChar();
    setTextCursor(cursor);
}

bool JSEditor::cursorMoveKeyEvent(QKeyEvent *e)
{
    QTextCursor cursor = textCursor();

    QTextCursor::MoveMode mode = QTextCursor::MoveAnchor;
    QTextCursor::MoveOperation op = QTextCursor::NoMove;

    if (e == QKeySequence::MoveToNextChar) {
            op = QTextCursor::Right;
    } else if (e == QKeySequence::MoveToPreviousChar) {
            op = QTextCursor::Left;
    } else if (e == QKeySequence::SelectNextChar) {
           op = QTextCursor::Right;
           mode = QTextCursor::KeepAnchor;
    } else if (e == QKeySequence::SelectPreviousChar) {
            op = QTextCursor::Left;
            mode = QTextCursor::KeepAnchor;
    } else if (e == QKeySequence::SelectNextWord) {
            op = QTextCursor::WordRight;
            mode = QTextCursor::KeepAnchor;
    } else if (e == QKeySequence::SelectPreviousWord) {
            op = QTextCursor::WordLeft;
            mode = QTextCursor::KeepAnchor;
    } else if (e == QKeySequence::SelectStartOfLine) {
            op = QTextCursor::StartOfLine;
            mode = QTextCursor::KeepAnchor;
    } else if (e == QKeySequence::SelectEndOfLine) {
            op = QTextCursor::EndOfLine;
            mode = QTextCursor::KeepAnchor;
    } else if (e == QKeySequence::SelectStartOfBlock) {
            op = QTextCursor::StartOfBlock;
            mode = QTextCursor::KeepAnchor;
    } else if (e == QKeySequence::SelectEndOfBlock) {
            op = QTextCursor::EndOfBlock;
            mode = QTextCursor::KeepAnchor;
    } else if (e == QKeySequence::SelectStartOfDocument) {
            op = QTextCursor::Start;
            mode = QTextCursor::KeepAnchor;
    } else if (e == QKeySequence::SelectEndOfDocument) {
            op = QTextCursor::End;
            mode = QTextCursor::KeepAnchor;
    } else if (e == QKeySequence::SelectPreviousLine) {
            op = QTextCursor::Up;
            mode = QTextCursor::KeepAnchor;
    } else if (e == QKeySequence::SelectNextLine) {
            op = QTextCursor::Down;
            mode = QTextCursor::KeepAnchor;
            {
                QTextBlock block = cursor.block();
                QTextLine line = currentTextLine(cursor);
                if (!block.next().isValid()
                    && line.isValid()
                    && line.lineNumber() == block.layout()->lineCount() - 1)
                    op = QTextCursor::End;
            }
    } else if (e == QKeySequence::MoveToNextWord) {
            op = QTextCursor::WordRight;
    } else if (e == QKeySequence::MoveToPreviousWord) {
            op = QTextCursor::WordLeft;
    } else if (e == QKeySequence::MoveToEndOfBlock) {
            op = QTextCursor::EndOfBlock;
    } else if (e == QKeySequence::MoveToStartOfBlock) {
            op = QTextCursor::StartOfBlock;
    } else if (e == QKeySequence::MoveToNextLine) {
            op = QTextCursor::Down;
    } else if (e == QKeySequence::MoveToPreviousLine) {
            op = QTextCursor::Up;
    } else if (e == QKeySequence::MoveToPreviousLine) {
            op = QTextCursor::Up;
    } else if (e == QKeySequence::MoveToStartOfLine) {
            op = QTextCursor::StartOfLine;
    } else if (e == QKeySequence::MoveToEndOfLine) {
            op = QTextCursor::EndOfLine;
    } else if (e == QKeySequence::MoveToStartOfDocument) {
            op = QTextCursor::Start;
    } else if (e == QKeySequence::MoveToEndOfDocument) {
            op = QTextCursor::End;
    } else {
        return false;
    }

    bool visualNavigation = cursor.visualNavigation();
    cursor.setVisualNavigation(true);

    if (op == QTextCursor::WordRight)
        camelCaseRight(cursor, mode);
    else if (op == QTextCursor::WordLeft)
        camelCaseLeft(cursor, mode);
    else if (!cursor.movePosition(op, mode) && mode == QTextCursor::MoveAnchor)
        cursor.clearSelection();
    cursor.setVisualNavigation(visualNavigation);

    setTextCursor(cursor);
    ensureCursorVisible();
    return true;
}

int JSEditor::position(PositionOperation posOp, int at) const
{
    QTextCursor tc = textCursor();

    if (at != -1)
        tc.setPosition(at);

    if (posOp == Current)
        return tc.position();

    switch (posOp) {
    case EndOfLine:
        tc.movePosition(QTextCursor::EndOfLine);
        return tc.position();
    case StartOfLine:
        tc.movePosition(QTextCursor::StartOfLine);
        return tc.position();
    case Anchor:
        if (tc.hasSelection())
            return tc.anchor();
        break;
    case EndOfDoc:
        tc.movePosition(QTextCursor::End);
        return tc.position();
    default:
        break;
    }

    return -1;
}

void JSEditor::setCursorPosition(int pos)
{
    QTextCursor tc = textCursor();
    tc.setPosition(pos);
    setTextCursor(tc);
}

void JSEditor::remove(int length)
{
    QTextCursor tc = textCursor();
    tc.setPosition(tc.position() + length, QTextCursor::KeepAnchor);
    tc.removeSelectedText();
}

void JSEditor::replace(int length, const QString &string)
{
    QTextCursor tc = textCursor();
    tc.setPosition(tc.position() + length, QTextCursor::KeepAnchor);
    tc.insertText(string);
}

void JSEditor::acceptNewSemanticInfo(const SemanticInfo &semanticInfo)
{

    m_semanticinfo = semanticInfo;
    Document::Ptr doc = semanticInfo.document;

    // create the ranges
    CreateRanges createRanges;
    m_semanticinfo.ranges = createRanges(document(), doc);

    // Refresh the ids
    FindIdDeclarations updateIds;
    m_semanticinfo.idLocations = updateIds(doc);

    //if (m_contextPane) {
    //    Node *newNode = m_semanticInfo.declaringMemberNoProperties(position());
    //    if (newNode) {
    //        m_contextPane->apply(editor(), semanticInfo.document, 0, newNode, true);
            //m_cursorPositionTimer->start(); //update text marker
    //    }
    //}

    // update outline
    //m_updateOutlineTimer->start();

    //if (Core::EditorManager::currentEditor() == editor())
     m_semanticHighlighter->rerun(m_semanticinfo);

    //emit semanticInfoUpdated();
}

QJSHighLight* JSEditor::get_high_light()
{
    return m_high_light;
}



void JSEditor::onDocumentUpdated(QmlJS::Document::Ptr doc)
{
    if (doc->ast())
    {
        m_semanticinfo_updater->update(doc, m_modelManager->snapshot());
        setExtraSelections(CodeWarningsSelection, QList<QTextEdit::ExtraSelection>());
    }
    else if (Document::isFullySupportedLanguage(doc->language()))
    {
        QList<QTextEdit::ExtraSelection> selections;
        appendExtraSelectionsForMessages(&selections, doc->diagnosticMessages(), document());
        setExtraSelections(CodeWarningsSelection, selections);
    }
    else
    {
        setExtraSelections(CodeWarningsSelection, QList<QTextEdit::ExtraSelection>());
    }
}

void JSEditor::updateSemanticInfo()
{
    m_updateSemanticInfoTimer->start();
}

QList<QTextEdit::ExtraSelection> JSEditor::extraSelections(ExtraSelectionKind kind) const
{
    return m_extraSelections[kind];
}

void JSEditor::updateSemanticInfoNow()
{

    m_updateSemanticInfoTimer->stop();

    m_semanticinfo_updater->reupdate(m_modelManager->snapshot());
}

void JSEditor::updateUses()
{
    m_updateUsesTimer->start();
}


void JSEditor::updateUsesNow()
{
    m_updateUsesTimer->stop();

    QList<QTextEdit::ExtraSelection> selections;
    foreach (const AST::SourceLocation &loc, m_semanticinfo.idLocations.value(wordUnderCursor()))
    {
        if (! loc.isValid())
            continue;

        QTextEdit::ExtraSelection sel;
        sel.format = m_occurrencesFormat;
        sel.cursor = textCursor();
        sel.cursor.setPosition(loc.begin());
        sel.cursor.setPosition(loc.end(), QTextCursor::KeepAnchor);
        qDebug(sel.cursor.selectedText().toLocal8Bit());
        selections.append(sel);
    }

    setExtraSelections(CodeSemanticsSelection, selections);
}

QString JSEditor::wordUnderCursor() const
{
    QTextCursor tc = textCursor();
    const QChar ch = document()->characterAt(tc.position() - 1);
    if (ch.isLetterOrNumber() || ch == QLatin1Char('_'))
        tc.movePosition(QTextCursor::Left);
    tc.movePosition(QTextCursor::StartOfWord);
    tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
    const QString word = tc.selectedText();
    return word;
}

bool JSEditor::open(const QString &fileName)
{
    QFile f(fileName);
    if(!f.open(QFile::ReadOnly))
    {
        return false;
    }

    m_file_name=fileName;
    setPlainText(f.readAll());
    f.close();
    return true;
}

QString JSEditor::fileName()
{
    return m_file_name;
}

void JSEditor::reparseDocument()
{
    m_updateDocumentTimer->start();
}

void JSEditor::reparseDocumentNow()
{
    m_updateDocumentTimer->stop();

    m_modelManager->updateSourceFiles(QStringList() << m_file_name, false);
}

void JSEditor::modificationChanged(bool changed)
{
    if (!changed && m_modelManager)
        m_modelManager->fileChangedOnDisk(m_file_name);
}

void JSEditor::updateCursorPositionNow()
{
    if (document() && m_semanticinfo.isValid())
    {
        //Node *oldNode = m_semanticinfo.declaringMemberNoProperties(m_oldCursorPosition);
        //Node *newNode = m_semanticInfo.declaringMemberNoProperties(position());
//        if (oldNode != newNode && m_oldCursorPosition != -1)
//            m_contextPane->apply(editor(), semanticInfo().document, 0, newNode, false);

//        if (m_contextPane->isAvailable(editor(), semanticInfo().document, newNode) &&
//            !m_contextPane->widget()->isVisible()) {
//            QList<TextEditor::RefactorMarker> markers = removeMarkersOfType<QtQuickToolbarMarker>(refactorMarkers());
//            if (UiObjectMember *m = newNode->uiObjectMemberCast()) {
//                const int start = qualifiedTypeNameId(m)->identifierToken.begin();
//                for (UiQualifiedId *q = qualifiedTypeNameId(m); q; q = q->next) {
//                    if (! q->next) {
//                        const int end = q->identifierToken.end();
//                        if (position() >= start && position() <= end) {
//                            TextEditor::RefactorMarker marker;
//                            QTextCursor tc(document());
//                            tc.setPosition(end);
//                            marker.cursor = tc;
//                            marker.tooltip = tr("Show Qt Quick ToolBar");
//                            marker.data = QVariant::fromValue(QtQuickToolbarMarker());
//                            markers.append(marker);
//                        }
//                    }
//                }
//            }
//            setRefactorMarkers(markers);
//        } else if (oldNode != newNode) {
//            setRefactorMarkers(removeMarkersOfType<QtQuickToolbarMarker>(refactorMarkers()));
//        }
        m_oldCursorPosition = position();

        setSelectedElements();
    }
}

void JSEditor::setSelectedElements()
{
    if (!receivers(SIGNAL(selectedElementsChanged(QList<QmlJS::AST::UiObjectMember*>,QString))))
        return;

    QTextCursor tc = textCursor();
    QString wordAtCursor;
    QList<UiObjectMember *> offsets;

    unsigned startPos;
    unsigned endPos;

    if (tc.hasSelection()) {
        startPos = tc.selectionStart();
        endPos = tc.selectionEnd();
    } else {
        tc.movePosition(QTextCursor::StartOfWord);
        tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);

        startPos = textCursor().position();
        endPos = textCursor().position();
    }

    if (m_semanticinfo.isValid()) {
        SelectedElement selectedMembers;
        QList<UiObjectMember *> members = selectedMembers(m_semanticinfo.document,
                                                          startPos, endPos);
        if (!members.isEmpty())
        {
            foreach (UiObjectMember *m, members) {
                offsets << m;
            }
        }
    }
    wordAtCursor = tc.selectedText();

    emit selectedElementsChanged(offsets, wordAtCursor);
}

void JSEditor::highlightSearchResults(const QString &txt, FindFlags findFlags)
{
    QString pattern = txt;
    if (pattern.size() < 2 && !(findFlags & FindWholeWords))
        pattern.clear();

    if (m_searchExpr.pattern() == pattern)
        return;
    m_searchExpr.setPattern(pattern);
    m_searchExpr.setPatternSyntax((findFlags & FindRegularExpression) ?
                                     QRegExp::RegExp : QRegExp::FixedString);
    m_searchExpr.setCaseSensitivity((findFlags & FindCaseSensitively) ?
                                       Qt::CaseSensitive : Qt::CaseInsensitive);
    m_findFlags = findFlags;

    //m_delayedUpdateTimer.start(50);
}

void JSEditor::setFindScope(const QTextCursor &start, const QTextCursor &end,
                                  int verticalBlockSelectionFirstColumn,
                                  int verticalBlockSelectionLastColumn)
{
    if (start != m_findScopeStart
            || end != m_findScopeEnd
            || verticalBlockSelectionFirstColumn != m_findScopeVerticalBlockSelectionFirstColumn
            || verticalBlockSelectionLastColumn != m_findScopeVerticalBlockSelectionLastColumn) {
        m_findScopeStart = start;
        m_findScopeEnd = end;
        m_findScopeVerticalBlockSelectionFirstColumn = verticalBlockSelectionFirstColumn;
        m_findScopeVerticalBlockSelectionLastColumn = verticalBlockSelectionLastColumn;
        viewport()->update();
    }
}
